﻿using System.Collections.Generic;

namespace Raven
{
    //------------------------------------------------------------------------
    //
    //  defines a cell containing a list of pointers to entities
    //------------------------------------------------------------------------
    public class Cell<T>
    {
        //all the entities inhabiting this cell
        public LinkedList<T> Members;

        //the cell's bounding box (it's inverted because the Window's default
        //co-ordinate system has a y axis that increases as it descends)
        public InvertedAABBox2D BBox;

        public Cell(Vector2 topleft, Vector2 botright)
        {
            Members = new LinkedList<T>();
            BBox = new InvertedAABBox2D(topleft, botright);
        }
    }

    public class CellSpacePartition<T> where T : GraphNode
    {
        //the required amount of cells in the space
        List<Cell<T>> m_Cells;

        //the number of cells the space is going to be divided up into
        private int m_iNumCellsX;
        private int m_iNumCellsY;

        private Fix m_dCellSizeX;
        private Fix m_dCellSizeY;

        //the width and height of the world space the entities inhabit
        Fix m_dSpaceWidth;
        Fix m_dSpaceHeight;

        //this is used to store any valid neighbors when an agent searches
        //its neighboring space
        List<T> m_Neighbors;

        public IEnumerator<T> GetEnumerator()
        {
            return m_Neighbors.GetEnumerator();
        }

        public CellSpacePartition(Fix width, //width of the environment
            Fix height, //height ...
            int cellsX, //number of cells horizontally
            int cellsY, //number of cells vertically
            int MaxEntitys) //maximum number of entities to add
        {
            //calculate bounds of each cell
            m_dCellSizeX = width / cellsX;
            m_dCellSizeY = height / cellsY;
            m_iNumCellsX = cellsX;
            m_iNumCellsY = cellsY;
            m_dSpaceWidth = width;
            m_dSpaceHeight = height;

            m_Neighbors = new List<T>(MaxEntitys);

            //create the cells
            m_Cells = new List<Cell<T>>();
            for (int y = 0; y < m_iNumCellsY; ++y)
            {
                for (int x = 0; x < m_iNumCellsX; ++x)
                {
                    Fix left = x * m_dCellSizeX;
                    Fix right = left + m_dCellSizeX;
                    Fix top = y * m_dCellSizeY;
                    Fix bot = top + m_dCellSizeY;

                    m_Cells.Add(new Cell<T>(new Vector2(left, top), new Vector2(right, bot)));
                }
            }
        }

        public void AddEntity(T ent)
        {
            int idx = PositionToIndex(ent.Pos());

            m_Cells[idx].Members.AddLast(ent);
        }

        public int PositionToIndex(Vector2 pos)
        {
            int idx = (int) (m_iNumCellsX * pos.x / m_dSpaceWidth) +
                      ((int) ((m_iNumCellsY) * pos.y / m_dSpaceHeight) * m_iNumCellsX);

            //if the entity's position is equal to vector2d(m_dSpaceWidth, m_dSpaceHeight)
            //then the index will overshoot. We need to check for this and adjust
            if (idx > m_Cells.Count - 1) idx = m_Cells.Count - 1;

            return idx;
        }

        //----------------------- CalculateNeighbors ----------------------------
        //
        //  This must be called to create the vector of neighbors.This method 
        //  examines each cell within range of the target, If the 
        //  cells contain entities then they are tested to see if they are situated
        //  within the target's neighborhood region. If they are they are added to
        //  neighbor list
        //------------------------------------------------------------------------
        public void CalculateNeighbors(Vector2 TargetPos, Fix QueryRadius)
        {
            //create the query box that is the bounding box of the target's query
            //area
            InvertedAABBox2D QueryBox = new InvertedAABBox2D(TargetPos - new Vector2(QueryRadius, QueryRadius),
                TargetPos + new Vector2(QueryRadius, QueryRadius));

            //iterate through each cell and test to see if its bounding box overlaps
            //with the query box. If it does and it also contains entities then
            //make further proximity tests.
            m_Neighbors.Clear();
            foreach (Cell<T> curCell in m_Cells)
            {
                //test to see if this cell contains members and if it overlaps the
                //query box
                if (curCell.BBox.isOverlappedWith(QueryBox) &&
                    curCell.Members.Count > 0)
                {
                    //add any entities found within query radius to the neighbor list
                    foreach (var it in curCell.Members)
                    {
                        if ((it.Pos() - TargetPos).sqrMagnitude <
                            QueryRadius * QueryRadius)
                        {
                            m_Neighbors.Add(it);
                        }
                    }
                }
            }
        }
    }
}